package crontab

import (
	"context"
	"fmt"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gcron"
	"github.com/gogf/gf/v2/os/gctx"
	"github.com/gogf/gf/v2/os/glog"
	"github.com/gogf/gf/v2/os/gtime"
	"github.com/gogf/gf/v2/util/guid"
	"path/filepath"
	"server/internal/consts"
	"server/internal/dao"
	"server/internal/library/snowflake"
	"server/internal/model/do"
	"server/internal/model/entity"
	"strings"
	"sync"
)

// 全局注册的定时任务
var crontabManager = _cronManager{
	tasks:   make(map[string]*TaskItem),
	loggers: make(map[string]*glog.Logger),
}

// Tasker 任务接口定义
type Tasker interface {
	Name() string
	Run(ctx context.Context, taskParams TaskParams) error
}

// 定时任务管理
type _cronManager struct {
	tasks        map[string]*TaskItem    //管理所有的任务
	loggers      map[string]*glog.Logger //管理任务对应的日志
	sync.RWMutex                         // 控制并发添加任务
}

type TaskItem struct {
	Name   string        //定时任务唯一名称
	Func   gcron.JobFunc //任务执行的方法
	Params string        //任务执行的参数：任务表定义是按逗号隔开传递多个参数
}

type TaskParams struct {
	Args   []string     //任务执行的参数
	Logger *glog.Logger //如果用这个日志打印则会在按任务名称+ID生成日志文件，实现对任务划分日志
}

// Logger 定时任务日志通用方法，根据配置文件会输入当前日志的关键信息，如配置文件中定义的参数CtxKeys: ["crontabSn", "crontabParams"]，在genCronCtx()中初始化声明
func Logger() *glog.Logger {
	return g.Log("crontab")
}

// genCronTaskSn 生成定时任务名称（编号）
func genCronTaskSn(name string, id int64) string {
	return fmt.Sprintf("%s_%d", name, id)
}

// genCronCtx 生成任务上下文,通过上下文传递job任务指定的请求参数
func genCronCtx(job entity.SystemCrontab) (c context.Context) {
	c = context.WithValue(gctx.New(), consts.CtxCrontabArgsKey, strings.Split(job.Params, ","))
	c = context.WithValue(c, consts.CtxCrontabSnKey, genCronTaskSn(job.Name, int64(job.Id)))
	return c
}

// genLogger 根据任务生成对应的文件
func genLoggerCtx(ctx context.Context) *glog.Logger {
	taskSn, ok := ctx.Value(consts.CtxCrontabSnKey).(string)
	if !ok {
		Logger().Panic(ctx, "genLoggerCtx get taskSn fail")
	}

	logger, ok := crontabManager.loggers[taskSn]
	if ok {
		return logger
	}

	taskLogger := g.Log("crontabTask") // 根据配置文件中设置的日志相关参数生成日志实例

	logger = glog.New()
	logger.SetFlags(glog.F_TIME_STD | glog.F_FILE_SHORT)
	_ = logger.SetConfig(taskLogger.GetConfig())
	_ = logger.SetPath(filepath.Join(logger.GetPath(), taskSn))

	crontabManager.Lock()
	defer crontabManager.Unlock()
	crontabManager.loggers[taskSn] = logger
	return logger
}

// CheckTaskPatternIsValid 检测任务表达式是否正常
func CheckTaskPatternIsValid(ctx context.Context, pattern string) bool {
	name := "temp_" + guid.S()
	_, err := gcron.AddOnce(ctx, pattern, func(ctx context.Context) {
		Logger().Debugf(ctx, "CheckTaskPatternIsValid[%s]: is ok", pattern)
	}, name)
	if err != nil {
		Logger().Debugf(ctx, "CheckTaskPatternIsValid error：%s，pattern[%s]", err.Error(), pattern)
		return false
	}

	// 测试通过后立即删除
	gcron.Remove(name)

	return true
}

// Register 定时任务注册
func Register(tasks []Tasker) {
	crontabManager.Lock()
	defer crontabManager.Unlock()
	for _, task := range tasks {
		name := task.Name()
		if _, ok := crontabManager.tasks[name]; ok {
			Logger().Debugf(gctx.GetInitCtx(), "crontab code [%s] repeat，register fail", name)
			return
		}
		Logger().Debugf(gctx.GetInitCtx(), "crontab code [%s] register success", name)

		crontabManager.tasks[name] = &TaskItem{
			Name: name,
			Func: taskExecute(task.Run),
		}
	}
}

// 任务执行，回调任务定义时的方法
func taskExecute(f func(ctx context.Context, taskParams TaskParams) (err error)) func(ctx context.Context) {
	return func(ctx context.Context) {
		args, ok := ctx.Value(consts.CtxCrontabArgsKey).([]string) //从上下文中解析传递的执行参数
		if !ok {
			Logger().Error(ctx, "crontab exec fail, params parse error")
			return
		}
		startTime := gtime.Now()
		params := TaskParams{}
		params.Args = args
		ctx = context.WithValue(ctx, consts.CtxCrontabBatchSn, snowflake.GenerateSerialNo()) //定义当前执行的批次号，会在日志中体现
		params.Logger = genLoggerCtx(ctx)
		err := f(ctx, params)
		duration := gtime.Now().Sub(startTime).Milliseconds() // 执行耗时
		if err != nil {
			params.Logger.Debugf(ctx, "crontab exce fail, consume time: %vms, error: %v，", duration, err)
			return
		}
		params.Logger.Debugf(ctx, "crontab exce sucess, consume time: %vms", duration)
	}
}

// StartTasks 启动定时任务
func StartTasks(jobs []entity.SystemCrontab) (err error) {
	// 读取数据库中的定时任务列表，与系统注册的任务列表对比，存在的才能执行（因为需要知道任务处理的方法）
	if len(jobs) == 0 {
		Logger().Debugf(gctx.GetInitCtx(), "database does not have crontab tasks")
		return
	}

	for _, job := range jobs {
		task, ok := crontabManager.tasks[job.Name]
		if !ok {
			Logger().Debugf(gctx.GetInitCtx(), "crontab[%s] unregister, start fail", job.Name)
			continue
		}

		taskSn := genCronTaskSn(task.Name, int64(job.Id))

		// 定时任务未添加则注册
		if gcron.Search(taskSn) == nil {
			var (
				t   *gcron.Entry
				ctx = genCronCtx(job)
			)
			switch job.Policy {
			case consts.CrontabPolicyNormal: // 普通定时任务
				t, err = gcron.Add(ctx, job.Pattern, task.Func, taskSn)

			case consts.CrontabPolicySingleton: //单例定时任务
				t, err = gcron.AddSingleton(ctx, job.Pattern, task.Func, taskSn)

			case consts.CrontabPolicyOnce: //执行一次的定时任务
				t, err = gcron.AddOnce(ctx, job.Pattern, task.Func, taskSn)

			case consts.CrontabPolicyMulti: // 执行多次的定时任务
				t, err = gcron.AddTimes(ctx, job.Pattern, job.Count, task.Func, taskSn)

			default:
				err = gerror.Newf("crontab[%s]policy[%d]isvalid", job.Name, job.Policy)

			}
			if err != nil {
				g.Log().Debugf(ctx, "crontab[%s]register fail: %s", job.Name, err.Error())
				continue
			}
			if t == nil {
				g.Log().Debugf(ctx, "crontab[%s]start fail", job.Name)
				continue
			}
		}

		//启动任务
		gcron.Start(taskSn)

		//单次和多次策略类型的任务，由于运行结束后会自动销毁，因此在启动成功后因标记为禁用，避免重复调用添加
		if job.Policy == consts.CrontabPolicyOnce || job.Policy == consts.CrontabPolicyMulti {
			_, err = dao.SystemCrontab.Ctx(gctx.GetInitCtx()).Data(do.SystemCrontab{Status: consts.CrontabStatusDisabled}).Where(do.SystemCrontab{Id: job.Id}).Update()
			if err != nil {
				Logger().Debugf(gctx.GetInitCtx(), "crontab[%s] run status fail: %s", job.Name, err.Error())
			}
		}

		Logger().Debugf(gctx.GetInitCtx(), "crontab[%s]start success", taskSn)
	}

	return nil
}

// RefreshTaskStatus 刷新一个定时任务状态
func RefreshTaskStatus(job entity.SystemCrontab) {
	if job.Id == 0 {
		Logger().Debugf(gctx.GetInitCtx(), "crontab job.id is empty, cannot refresh crontab task status")
		return
	}

	// 启动项目
	if job.Status == consts.CrontabStatusEnabled {
		if err := ResetStart(job); err != nil {
			Logger().Debugf(gctx.GetInitCtx(), "crontab[%s]exec fail：%s", job.Name, err.Error())
		}
		return
	}

	StopTask(job)
	Logger().Debugf(gctx.GetInitCtx(), "crontab[%s]stop", job.Name)
}

// ResetStart 重启一个定时任务：停止->删除->启动
func ResetStart(job entity.SystemCrontab) error {
	// 停止任务
	StopTask(job)

	// 删除任务
	DeleteTask(job)

	// 启动任务
	return StartTask(job)
}

// StartTask 启动一个已经注册的定时任务
func StartTask(job entity.SystemCrontab) error {
	return StartTasks([]entity.SystemCrontab{job})
}

// StopTask 停止一个已经添加的定时任务
func StopTask(job entity.SystemCrontab) {
	task, ok := crontabManager.tasks[job.Name]
	if !ok {
		Logger().Debugf(gctx.GetInitCtx(), "crontab[%s]unregister, stop fail", job.Name)
		return
	}
	taskSn := genCronTaskSn(task.Name, int64(job.Id))
	if gcron.Search(taskSn) != nil {
		gcron.Stop(taskSn)
	}
}

// DeleteAllTasks 移除所有注册的定时任务
func DeleteAllTasks() {
	for _, cron := range gcron.Entries() {
		Logger().Debugf(gctx.GetInitCtx(), "crontab[%v]removed", cron.Name)
		gcron.Remove(cron.Name)
	}
}

// DeleteTask 删除定时任务
func DeleteTask(job entity.SystemCrontab) {
	sn := genCronTaskSn(job.Name, int64(job.Id))
	for _, cron := range gcron.Entries() {
		if sn == cron.Name {
			Logger().Debugf(gctx.GetInitCtx(), "crontab[%v]removed", cron.Name)
			gcron.Remove(cron.Name)
		}
	}
}

// GetRegisterCrontabList 获取已注册的定时任务列表
func GetRegisterCrontabList() []string {
	items := make([]string, 0)
	for _, task := range crontabManager.tasks {
		items = append(items, task.Name)
	}
	return items
}
